/**
 * @file dragdroplistbox.h
 * @brief 
 *
 * 
 *
 * @author 1823419256@qq.com
 * @version 1.0
 * @date 2011
*/

#ifndef MYBOOK_CTRLS_DRAGDROPLISTBOX_H_
#define MYBOOK_CTRLS_DRAGDROPLISTBOX_H_

#include "dragdropimpl.h"
#include <shellapi.h>

template <class T>
class CDragDropListBox : public CIDropTarget
{
public:
	enum { NO_DRAW_LINE = -2 };

	CDragDropListBox() : CIDropTarget(false)
	{
		m_nDropIndex   = -1;
		m_nPreDrawLine = NO_DRAW_LINE;
		m_bDragMode    = FALSE;
		m_pDropTarget  = NULL;

		m_bMouseOver   = FALSE;
	}

public:
	BEGIN_MSG_MAP(CDragDropListBox<T>)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		T* pThis = static_cast<T*>(this);

		bHandled = FALSE;
		InitDragDrop(pThis->GetFmtVector());

		return 0;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		T* pThis = static_cast<T*>(this);

		bHandled = FALSE;

		RevokeDragDrop(pThis->m_hWnd); //calls release

		m_pDropTarget->Release();
		m_pDropTarget = NULL;

		return 0;
	}

	LRESULT OnVScroll(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		bHandled = FALSE;

		m_nPreDrawLine = NO_DRAW_LINE;

		return 0;
	}

	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		T* pThis = static_cast<T*>(this);

		bHandled = FALSE;

		if (!m_bMouseOver)
		{
			m_bMouseOver = TRUE;
			
			TRACKMOUSEEVENT tme;
			tme.cbSize = sizeof (tme);
			tme.dwFlags = TME_LEAVE;
			tme.hwndTrack = pThis->m_hWnd;

			_TrackMouseEvent (&tme);
		}

		CPoint pt(lParam);
		if (pThis->CanDragDrop(pt))
			m_bDragMode = TRUE;

		return 0;
	}

	LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		bHandled = FALSE;
		m_bDragMode = FALSE;

		return 0;
	}

	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		T* pThis = static_cast<T*>(this);

		bHandled = FALSE;

		if (!m_bMouseOver)
		{
			m_bMouseOver = TRUE;

			TRACKMOUSEEVENT tme;
			tme.cbSize = sizeof (tme);
			tme.dwFlags = TME_LEAVE;
			tme.hwndTrack = pThis->m_hWnd;

			_TrackMouseEvent(&tme);
		}

		if (m_bDragMode && (wParam & MK_LBUTTON))	
		{	
			m_bDragMode = FALSE;

			CIDropSource* pdsrc = new CIDropSource;
			if (pdsrc == NULL) return 0;
			pdsrc->AddRef();

			CIDataObject* pdobj = new CIDataObject(pdsrc);
			if (pdobj == NULL) return 0;
			pdobj->AddRef();

			if (!pThis->DoSetData(pdobj))
			{
				pdsrc->Release();
				pdobj->Release();

				return 0;
			}

			POINT ptOffset;
			DWORD dwColorKey;
			HBITMAP hBitmap = pThis->GetDragImage(ptOffset, dwColorKey);
			m_DragSrcHelper.InitializeFromBitmap(hBitmap, ptOffset, pdobj, dwColorKey);

			DWORD dwEffect;
			HRESULT hr = ::DoDragDrop(pdobj, pdsrc, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);
			pdsrc->Release();
			pdobj->Release();

			bHandled = TRUE;
		}

		return 0;
	}

	LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		bHandled = FALSE;

		if (m_bDragMode)
			m_bDragMode = FALSE;

		return 0;
	}

protected:
	void ActivateWindow()
	{
		T* pThis = static_cast<T*>(this);

		HWND hWnd = pThis->GetParent();
		if (hWnd)
		{
			::SetForegroundWindow(hWnd);
			::BringWindowToTop(hWnd);
			::SetActiveWindow(hWnd);
		}
	}

	int IndexFromPoint(POINT pt)
	{
		T* pThis = static_cast<T*>(this);

		pThis->ClientToScreen(&pt);
		BOOL bAutoScroll = FALSE;
		return ::LBItemFromPt(pThis->m_hWnd, pt, bAutoScroll);

		return -1;
	}

	void ResetAll()
	{
		T* pThis = static_cast<T*>(this);

		HDC hDC = pThis->GetDC();
		ClearOneLine(hDC, m_nPreDrawLine);
		m_nPreDrawLine = NO_DRAW_LINE;
		pThis->ReleaseDC(hDC);
		m_nDropIndex = LB_ERR;
	}

	void DrawDragLines(int Index)
	{
		T* pThis = static_cast<T*>(this);

		m_nDropIndex = Index;

		if (m_nPreDrawLine == m_nDropIndex)
			return;

		HDC hDC = pThis->GetDC();	

		ClearOneLine(hDC, m_nPreDrawLine);
		if (DrawOneLine(hDC, m_nDropIndex))
			m_nPreDrawLine = m_nDropIndex;
		else
			m_nPreDrawLine = NO_DRAW_LINE;

		pThis->ReleaseDC(hDC);
	}

	void ClearOneLine(HDC hDC, int nIndex)
	{
		T* pThis = static_cast<T*>(this);

		if (nIndex == NO_DRAW_LINE)
			return;

		RECT rcClient;
		pThis->GetClientRect(&rcClient);

		RECT rcItem;
		if (nIndex != LB_ERR)
		{
			pThis->GetItemRect(nIndex, &rcItem);
		}
		else
		{
			int nCount = pThis->GetCount();

			if (nCount > 0)
			{
				pThis->GetItemRect(nCount - 1, &rcItem);

				rcItem.top = rcItem.bottom;
				rcItem.bottom += 2;
			}
			else
			{
				rcItem = rcClient;
				rcItem.bottom = rcItem.top + 2;
			}
		}

		InvalidateRect(pThis->m_hWnd, &rcItem, TRUE);
	}

	BOOL DrawOneLine(HDC hDC, int nIndex)
	{
		T* pThis = static_cast<T*>(this);

		BOOL bDrawn = FALSE;
		if (nIndex == NO_DRAW_LINE)
			return bDrawn;

		RECT rcClient;
		pThis->GetClientRect(&rcClient);

		int nDrawMode = ::SetROP2(hDC, R2_NOT);

		RECT rcItem;
		HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		HPEN hOldPen = (HPEN)::SelectObject(hDC, hPen);
		if (nIndex != LB_ERR)
		{
			pThis->GetItemRect(nIndex, &rcItem);
			POINT pt = { rcItem.left, rcItem.top };

			if (::PtInRect(&rcClient, pt))
			{
				::MoveToEx(hDC, rcItem.left, rcItem.top, NULL);
				::LineTo(hDC, rcItem.right, rcItem.top);
				::MoveToEx(hDC, rcItem.left, rcItem.top + 1, NULL);
				::LineTo(hDC, rcItem.right, rcItem.top + 1);

				bDrawn = TRUE;
			}
		}
		else
		{
			int nCount = pThis->GetCount();

			if (nCount > 0)
			{
				pThis->GetItemRect(nCount - 1, &rcItem);
				POINT pt = { 0, rcItem.bottom };
				if (::PtInRect(&rcClient, pt))
				{
					::MoveToEx(hDC, rcItem.left, rcItem.bottom, NULL);
					::LineTo(hDC, rcItem.right, rcItem.bottom);
					::MoveToEx(hDC, rcItem.left, rcItem.bottom + 1, NULL);
					::LineTo(hDC, rcItem.right, rcItem.bottom + 1);

					bDrawn = TRUE;
				}
			}
			else
			{
				::MoveToEx(hDC, rcClient.left, rcClient.top, NULL);
				::LineTo(hDC, rcClient.right, rcClient.top);
				::MoveToEx(hDC, rcClient.left, rcClient.top + 1, NULL);
				::LineTo(hDC, rcClient.right, rcClient.top + 1);

				bDrawn = TRUE;
			}
		}

		::SelectObject(hDC, hOldPen);
		DeleteObject(hPen);

		::SetROP2(hDC, nDrawMode);

		return bDrawn;
	}	

	// IDropTarget
public:
	virtual HRESULT STDMETHODCALLTYPE DragEnter(IDataObject __RPC_FAR *pDataObj, DWORD grfKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect)
	{
		T* pThis = static_cast<T*>(this);

		HRESULT hr = CIDropTarget::DragEnter(pDataObj, grfKeyState, pt, pdwEffect);

		if (hr == S_OK && DROPEFFECT_NONE != *pdwEffect)
		{
			POINT point = { pt.x, pt.y };
			pThis->ScreenToClient(&point);
			int nItem = IndexFromPoint(point);

			ActivateWindow();
			DrawDragLines(nItem);
		}

		return hr;
	}

	virtual HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect)
	{
		T* pThis = static_cast<T*>(this);

		HRESULT hr = CIDropTarget::DragOver(grfKeyState, pt, pdwEffect);

		if (hr == S_OK && DROPEFFECT_NONE != *pdwEffect)
		{
			POINT point = { pt.x, pt.y };
			pThis->ScreenToClient(&point);
			int nItem = IndexFromPoint(point);

			DrawDragLines(nItem);
		}

		return hr;
	}

	virtual HRESULT STDMETHODCALLTYPE DragLeave(void)
	{
		ResetAll();

		return CIDropTarget::DragLeave();
	}
/*
	virtual bool OnDrop(FORMATETC* pFmtEtc, STGMEDIUM& med, DWORD *pdwEffect, HWND hWndFrom, std::vector<int> vecIndices)
	{
		T* pThis = static_cast<T*>(this);

		if(pFmtEtc->cfFormat == CF_HDROP && med.tymed == TYMED_HGLOBAL)
		{
			int nCount = pThis->GetCount();
			int nDropHere = (m_nDropIndex == -1) ? nCount : m_nDropIndex;

			HDROP hDrop = (HDROP)GlobalLock(med.hGlobal);
			if(hDrop != NULL)
			{
				std::vector<CString> vecText;

				TCHAR szFileName[MAX_PATH];
				UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); 
				for(UINT i = 0; i < cFiles; ++i)
				{
					DragQueryFile(hDrop, i, szFileName, sizeof(szFileName)); 
					//InsertString(nCurItem++, szFileName);
					//::UpdateWindow(m_hTargetWnd);
					vecText.push_back(szFileName);
				} 

				if (*pdwEffect ==  DROPEFFECT_MOVE)
					pThis->DoDropMove(nDropHere, vecText, hWndFrom, vecIndices);
				else
					pThis->DoDropInsert(nDropHere, vecText, hWndFrom, vecIndices);

				//DragFinish(hDrop); // base class calls ReleaseStgMedium
			}

			GlobalUnlock(med.hGlobal);
		}


		ClearAll();

		return true; //let base free the medium
	}
*/
	virtual bool OnDrop(FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD *pdwEffect)
	{
		T* pThis = static_cast<T*>(this);

		LPBYTE lpData = (LPBYTE)GlobalLock(medium.hGlobal);
		ULONG nDataSize = GlobalSize(medium.hGlobal);
		bool bRet = pThis->DoDrop(pFmtEtc->cfFormat, lpData, nDataSize);
		GlobalUnlock(medium.hGlobal);

		ResetAll();

		return bRet;
	}

	bool InitDragDrop(std::vector<CLIPFORMAT>& vecFmt)
	{
		T* pThis = static_cast<T*>(this);

		m_pDropTarget = static_cast<CIDropTarget*>(this);
		m_pDropTarget->AddRef();
		m_pDropTarget->SetTargetWnd(pThis->m_hWnd);

		if (FAILED(RegisterDragDrop(pThis->m_hWnd, m_pDropTarget))) // calls addref
		{
			m_pDropTarget->Release();
			m_pDropTarget = NULL;
			return false;
		}

		FORMATETC fmt = { 0 };
		//fmt.cfFormat  = cf;
		fmt.dwAspect  = DVASPECT_CONTENT;
		fmt.lindex    = -1;
		fmt.tymed     = TYMED_HGLOBAL;

		std::vector<CLIPFORMAT>::const_iterator it = vecFmt.begin();
		for (; it != vecFmt.end(); ++it)
		{
			fmt.cfFormat = *it;
			m_pDropTarget->AddSuportedFormat(fmt);
		}

		return true;
	}

	BOOL CacheDragFiles(CIDataObject* pdobj, std::vector<CString>& vec)
	{
		T* pThis = static_cast<T*>(this);
		return TRUE;
	}

protected:
	int m_nDropIndex;
	int m_nPreDrawLine;
	BOOL m_bDragMode;
	BOOL m_bMouseOver;
	CIDropTarget* m_pDropTarget;
	CDragSourceHelper m_DragSrcHelper;
};

#endif // MYBOOK_CTRLS_DRAGDROPLISTBOX_H_